/*
*
* NHood
*
* This class represents a neighbourhood
*
*/

class NHood extends PlotObject {
  
  // "simple" attributes -- these are assigned directly from data
  String name, desc, nnum;
  ArrayList coords;
  ArrayList coordsxy = new ArrayList();
  int[] femalePop = new int[19];
  int[] malePop = new int[19];
  int dwellingOwned, dwellingRented, dwellingNoResponse;
  int structureSingleDetached, structureDuplex, structureRowHouse, structureApartment5Plus, structureApartmentLess5;
  int structureManufacturedHome, structureRoomingHouse, structureHotel, structureOther, structureNoResponse;
  int statusOccupied, statusUnoccupied, statusVacantLot, statusUnderConstruction, statusNoLongerInUse, statusNoResponse;
  int aveIncome, medianIncome;
  float percentSingleDetached, percentDuplexRowHouse, percentApt5Plus, percentAptLess5;
  float percentOccupied, percentUnoccupied, percentVacantLot, percentUnderConstruction, percentNoLongerInUse;
  
  // "calculated" attributes -- these are calculated using simple attributes
  float aveAge;
  int pop; 
  float percentOwn, percentRent;
  float peoplePerDwelling;
  float totalStructures;
  
  // ------------------------------------------
  // constructor
  // ------------------------------------------
  NHood(String n, String d, String nn, ArrayList cs) {
    name = n;
    desc = d;
    nnum = nn;
    coords = cs;
    
    // iterate through polygons and condition using Douglas Peucker method
    for(int i = 0; i < coords.size(); i++) {
      
      float[][] poly;
      
      // get polygon 
      poly = (float[][]) coords.get(i);      
      
      // subject to douglasPeucker method
      poly = douglasPeucker(poly, 0.004);

      // store
      coords.remove(i);
      coords.add(i, poly);
      
      // convert coordinates to xy space and store in coordsxy arraylist
      float[][] polyxy = new float[poly.length][poly[0].length];
      for(int j = 0; j < poly.length; j++) {
        polyxy[j] = convLL(poly[j]);
      }
      coordsxy.add(polyxy);
      
    } // for
    
    //println("coords.size: " + coords.size() + ", coordsxy.size: " + coordsxy.size()); /////////
    //println("coords[0].length: " + ((float[][]) coords.get(0)).length + ", coordsxy[0].length: " + ((float[][]) coordsxy.get(0)).length); /////////
          
  }  // constructor method
  
  // importCensusData method -- takes census data array and assigns to attributes
  void importCensusData(String[] cda) { 
    
    // assign data
    int sumAge = 0;
    pop = 0;
    
    // read population figures into arrays, and calculate total and average population
    for(int i = 1; i <= 19; i++) {
      femalePop[i - 1] = int(cda[i]);
      malePop[i - 1] = int(cda[i + 19]);
      pop += int(cda[i]) + int(cda[i + 19]);
      // don't add last item in array, as this is people who didn't specify an age
      if(i <= 18) {
        int tSum = int(cda[i]) + int(cda[i + 19]);
        sumAge += (2 + (i - 1) * 5) * tSum;
      } // if 
    } // for loop
    
    // check if population 0, or completely composed of "non specified" respondents
    // if so, assing "no data" flag value of -1
    if(pop == 0) {
      aveAge = -1;
    } else if((pop - int(cda[19]) - int(cda[38])) == 0) {
      aveAge = -1;    
    } else {
      // need to subtract numbers of people who didn't specify age when calculating average
      aveAge = float(sumAge) / float(pop - int(cda[19]) - int(cda[38]));
    } // if
    
    // directly assign remaining simple attributes
    dwellingOwned = int(cda[39]);
    dwellingRented = int(cda[40]);
    dwellingNoResponse = int(cda[41]);
    structureSingleDetached = int(cda[42]);
    structureDuplex = int(cda[43]);
    structureRowHouse = int(cda[44]);
    structureApartment5Plus = int(cda[45]);
    structureApartmentLess5 = int(cda[46]);
    structureManufacturedHome = int(cda[47]);
    structureRoomingHouse = int(cda[48]);
    structureHotel = int(cda[49]);
    structureOther = int(cda[50]);
    structureNoResponse = int(cda[51]);
    statusOccupied = int(cda[52]);
    statusUnoccupied = int(cda[53]);
    statusVacantLot = int(cda[54]);
    statusUnderConstruction = int(cda[55]);
    statusNoLongerInUse = int(cda[56]);
    statusNoResponse = int(cda[57]);
    
    // calculated attributes
    // a value of -1 is used as a flag to indicate that metric isn't applicable to neighbourhood in question
    if(((dwellingOwned == 0) && (dwellingRented == 0)) || ((dwellingOwned + dwellingRented) < 30)) {
      percentOwn = -1;
      percentRent = -1;
    } else {
      percentOwn = float(dwellingOwned) / float(dwellingOwned + dwellingRented) * 100;
      percentRent = float(dwellingRented) / float(dwellingOwned + dwellingRented) * 100;
    }
    if((dwellingOwned + dwellingRented + dwellingNoResponse) < 30) {
      peoplePerDwelling = -1;
    } else {
      peoplePerDwelling = float(pop) / float(dwellingOwned + dwellingRented + dwellingNoResponse);
    }
    
    totalStructures = structureSingleDetached + structureDuplex + structureRowHouse + structureApartment5Plus + structureApartmentLess5 +
      + structureManufacturedHome + structureRoomingHouse + structureHotel + structureOther + structureNoResponse;
    if(totalStructures == 0) {
      totalStructures = -1;
      percentSingleDetached = -1;
      percentDuplexRowHouse = -1; 
      percentApt5Plus = -1;
      percentAptLess5 = -1;
    } else {
      percentSingleDetached = structureSingleDetached / (totalStructures - structureNoResponse) * 100;
      percentDuplexRowHouse = (structureDuplex + structureRowHouse) / (totalStructures - structureNoResponse) * 100;
      percentApt5Plus = structureApartment5Plus / (totalStructures - structureNoResponse) * 100;
      percentAptLess5 = structureApartmentLess5 / (totalStructures - structureNoResponse) * 100;
    }
    
    int totalStatus = statusOccupied + statusUnoccupied + statusVacantLot + statusUnderConstruction + statusNoLongerInUse;
    if(totalStatus <= 0) {
      percentOccupied = -1;
      percentUnoccupied = -1;
      percentVacantLot = -1; 
      percentUnderConstruction = -1; 
      percentNoLongerInUse = -1;
    } else {
      percentOccupied = float(statusOccupied) / float(totalStatus) * 100;
      percentUnoccupied = float(statusUnoccupied) / float(totalStatus) * 100;
      percentVacantLot = float(statusVacantLot) / float(totalStatus) * 100;
      percentUnderConstruction = float(statusUnderConstruction) / float(totalStatus) * 100;
      percentNoLongerInUse = float(statusNoLongerInUse) / float(totalStatus) * 100;
    } // if
    
  } // importCensusData
  
  // -----------------------------------------
  // importOtherData 
  // takes "other" data array and assigns to attributes
  // -----------------------------------------
  void importOtherData(String[] oda) { 
    
    aveIncome = int(oda[1]);
    medianIncome = int(oda[2]);
    
  } // importOtherData
    
  // importNullCensusData method -- for "neighbourhoods" in KML file without census data; sets all
  // attributes to -1, which is used a flag for "no data"
  void importNullCensusData() {
    
    // simple attributes
    dwellingOwned = -1;
    dwellingRented = -1;
    dwellingNoResponse = -1;
    structureSingleDetached = -1;
    structureDuplex = -1;
    structureRowHouse = -1;
    structureApartment5Plus = -1;
    structureApartmentLess5 = -1;
    structureManufacturedHome = -1;
    structureRoomingHouse = -1;
    structureHotel = -1;
    structureOther = -1;
    structureNoResponse = -1;
    statusOccupied = -1;
    statusUnoccupied = -1;
    statusVacantLot = -1;
    statusUnderConstruction = -1;
    statusNoLongerInUse = -1;
    statusNoResponse = -1;
    
    // calculated attributes
    pop = -1;
    aveAge = -1;
    percentOwn = -1;
    percentRent = -1;
    peoplePerDwelling = -1;
    
    totalStructures = -1;
    percentSingleDetached = -1;
    percentDuplexRowHouse = -1; 
    percentApt5Plus = -1;
    percentAptLess5 = -1;
    
    percentOccupied = -1;
    percentUnoccupied = -1;
    percentVacantLot = -1; 
    percentUnderConstruction = -1; 
    percentNoLongerInUse = -1;
    
  } // importNullCensusData
  
  // -----------------------------------------------------
  // importNullOtherData
  // see importNullCensusData for the purpose of this method;
  // this one does the same thing, but with data from a different source
  // ------------------------------------------------------
  void importNullOtherData() {
    
    aveIncome = -1;
    medianIncome = -1;
    
  } // importNullOtherData
  
  // ------------------------------------------------------
  // draw
  // called during each iteration of draw loop
  // ------------------------------------------------------
  void draw(int dMode, float[][] maxMins, ColourManager colourMan) {
    
    // declare variables
    float[] xy = new float[2];
    float[][] poly;
    
    // iterate through polygons
    for(int i = 0; i < coords.size(); i++) {
      
      // get polygon 
      poly = (float[][]) coords.get(i);
      
      // draw polygon
      setColour(dMode, maxMins, colourMan);
      beginShape();
      
      for(int j = 0; j < poly.length; j++) {

        xy = convLL(poly[j]);
        vertex(xy[0], xy[1]);
        
        // DEBUG -- PLOT DATA POINTS
//        stroke(0, 100); ////////////////////////
//        point(xy[0], xy[1]); ///////////////////
//        stroke(50, 50, 50, 75); ////////////////
        
      } // for loop j
      
      endShape(CLOSE);
      
    } // for loop i
      
  } // draw method
  
  // -------------------------------------------------
  // setColour
  // retrieves fill and stroke colours based on drawing mode
  // -------------------------------------------------
  void setColour(int dMode, float[][] maxMins, ColourManager colourMan) {
    
    //int[] col = new int[3];
    color col;
    
    // case statement to direct action based on drawing mode
    switch(dMode) {
      // default mode
      case 0 :
        fill(dFil[0], dFil[1], dFil[2]);
        stroke(strok[0], strok[1], strok[2], strok[3]);
        break;
      // average age
      case 1 :
        // check if neighbourhood has peoplePerDwelling data; if not, fill w/ noDataFil
        if(aveAge == -1) {
          // fill with NoDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], aveAge);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
      // percentage of renters
      case 2 :
        // check if neighbourhood has peoplePerDwelling data; if not, fill w/ noDataFil
        if(percentRent == -1) {
          // fill with NoDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentRent);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
      // percentage of owners
      case 3 :
        // check if neighbourhood has peoplePerDwelling data; if not, fill w/ noDataFil
        if(percentOwn == -1) {
          // fill with NoDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentOwn);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
      // people per dwelling
      case 4 :
        // check if neighbourhood has peoplePerDwelling data; if not, fill w/ noDataFil
        if(peoplePerDwelling == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], peoplePerDwelling);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
      // average income
      case 5 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(aveIncome == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], aveIncome);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
      // median income
      case 6 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(medianIncome == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], medianIncome);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // total number of structures
       case 7 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(totalStructures == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], totalStructures);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent single family
       case 8 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentSingleDetached == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentSingleDetached);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent duplex/rowhouse
       case 9 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentDuplexRowHouse == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentDuplexRowHouse);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent apartments >= 5 stories
       case 10 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentApt5Plus == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentApt5Plus);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent apartments < 5 stories
       case 11 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentAptLess5 == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentAptLess5);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent occupied
       case 12 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentOccupied == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentOccupied);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent unoccupied
       case 13 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentUnoccupied == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentUnoccupied);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent under construction
       case 14 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentUnderConstruction == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentUnderConstruction);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent no longer in use
       case 15 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentNoLongerInUse == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentNoLongerInUse);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
       // percent vacant lots
       case 16 :
        // check if neighbourhood has aveIncome data; if not, fill w/ noDataFil
        if(percentVacantLot == -1) {
          // fill with noDataFil
          fill(noDataFil[0], noDataFil[1], noDataFil[2]);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        } else {
          // get colour value and apply
          col = colourMan.getColour(dMode, maxMins[dMode][0], maxMins[dMode][1], percentVacantLot);
          fill(col);
          stroke(strok[0], strok[1], strok[2], strok[3]);
          break;
        }
    } // switch

  } // setColour method
  
  // -----------------------------------------
  // douglasPeucker
  // this method is used to remove unnecessary points
  // from the plot, using the Douglas-Peucker algorithm
  // -----------------------------------------
  float[][] douglasPeucker (float[][] points, float epsilon) {
    
    // declare variables
    float dmax, d;
    int index;
    float[][] points1, points2;
    float[][] out;
    
    // calculate point with maximum orthogonal distance from line
    // between first and last point in "points"
    dmax = 0;
    index = 0;
    
    for(int i = 1; i < (points.length - 1); i++) {
      
      // check if first and last point are coincident (which will be the case for closed loops)
      // if so, use absolute distance from the coincident points in place of orhotgonal distance
      if(((points[0][0] - points[points.length - 1][0]) == 0) && ((points[0][0] - points[points.length - 1][0]) == 0)) {
        d = sqrt(sq(points[i][0] - points[0][0]) + sq(points[i][1] - points[0][1]));
      } else {
        // calculate orthogonal distance; store largest distance and index of point
        d = orthogonalDistance(points[i], points[0], points[points.length - 1]);
      }
      if(d > dmax) {
        dmax = d;
        index = i;
      } // if
    } // for
    
    // if dmax is greather than epsilon (tunable parameter), divide points into arrays
    // at location of dmax and recursively call method on both arrays
    if(dmax >= epsilon) {
      points1 = douglasPeucker(((float[][]) subset(points, 0, (index + 1 ))), epsilon);
      points2 = douglasPeucker(((float[][]) subset(points, index, (points.length - index))), epsilon);
      
      // store points calculate above in output variable
      out = (float[][]) concat(shorten(points1), points2);
    } else {
      // if no points fall outside dmax, return only first and last points in "points"
      //out = { points[0], points[points.length - 1] };
      out = new float[2][2];
      out[0] = points[0];
      out[1] = points[points.length - 1];
    } // if
    
    // return output variable
    return out;
    
  } // douglasPeucker algorithm
  
  // ------------------------------------
  // orthogonalDistance
  // calculates orthogonal distance between a line
  // and a point. Used for douglasPeucker method above
  // ------------------------------------
  float orthogonalDistance(float[] poin, float[] firstPoint, float[] lastPoint) {
    
    // declare variables
    float out;
    //float[] v1, v2;
    float dotProd, l1, l2, sinTheta;
    
    // convert lat/long to pixel coordinates
    poin = convLL(poin);
    firstPoint = convLL(firstPoint);
    lastPoint = convLL(lastPoint);
    
    // calculate dot product of vectors between firstPoint and poin (v1) and
    // firstPoint and lastPoint (v2)
    float[] v2 = { (lastPoint[0] - firstPoint[0]), (lastPoint[1] - firstPoint[1]) };
    float[] v1 = { (poin[0] - firstPoint[0]), (poin[1] - firstPoint[1]) };
    dotProd = v1[0] * v2[0] + v1[1] * v2[1];

    // calculate lengths between firstPoint and poin (l1) and firstPoint and
    // lastPoint (l2)
    l1 = sqrt(sq(v1[0]) + sq(v1[1]));
    l2 = sqrt(sq(v2[0]) + sq(v2[1]));
    
    // calculate sin of angle between v1 and v2. if dotProd = 0, set sinTheta to 0
    if(dotProd == 0) {
      sinTheta = 0;
    } else {
      sinTheta = sqrt(1 - sq(dotProd / (l1 * l2)));
    } // if
    // if sinTheta very small, set to 0
    // this is a bit of a hack
    if(sinTheta < 0.001) {
      sinTheta = 0;
    } // if
    
    // calculate distance and return;
    out = l1 * sinTheta;
    return out;    
    
  } // orthogonalDistance
  
  // ---------------------------------------
  // checkMouseOver
  // checks if mouse is positioned over neighbourhood
  // if so, displays neighbourhood name, description, and
  // current metric value
  // ---------------------------------------
  void checkMouseOver(float[] poin, NHoodInfoUI nhui, int dMode) {
    
    // iterate through neighbourhood's polygons and check each
    for(int i = 0; i < coords.size(); i++) {
      
      float[][] poly;
      String sCurVal = "";
      float curVal = -2;
      
      // get polygon and remove last (duplicate) coordinate
      poly = (float[][]) coordsxy.get(i);
      poly = (float[][]) shorten(poly);      
      
      // test polygon; if mouse is inside, update neighbourhood information
      if(pointInPolygon(poly, poin)) {

        // determine current metric value based on drawing mode
        switch(dMode) {
          // default mode
          case 0 :  
            curVal = -2;
            break;
          // average age
          case 1 : 
            curVal = aveAge;
            break;
          // percent renters
          case 2 :
            curVal = percentRent;
            break;
          // percent owners
          case 3 :
            curVal = percentOwn;
            break;
          // people per dwelling
          case 4 : 
            curVal = peoplePerDwelling;
            break;
          // average income
          case 5 :
            curVal = aveIncome;
            break;
          // median income
          case 6 :
            curVal = aveIncome;
            break;
          // total number of structures
          case 7 :
            curVal = totalStructures;
            break;
          // percent single family
          case 8 :
            curVal = percentSingleDetached;
            break;
          // percent duplex/rowhouse
          case 9 :
            curVal = percentDuplexRowHouse;
            break;
          // percent apartments >= 5 stories
          case 10 :
            curVal = percentApt5Plus;
            break;
          // percent apartments < 5 stories
          case 11 :
            curVal = percentAptLess5;
            break;
          // occupied
          case 12 :
            curVal = percentOccupied;
            break;
          // percent unoccupied
          case 13 :
            curVal = percentUnoccupied;
            break;
          // percent under construction
          case 14 :
            curVal = percentUnderConstruction;
            break;
          // percent no longer in use
          case 15 :
            curVal = percentNoLongerInUse;
            break;
          // percent vacant lots
          case 16 :
            curVal = percentVacantLot;
            break;
        } // switch
        
        // check for "no data" flag, or 
        // flag indicating field should be left blank
        if(curVal == -1) {
          sCurVal = "no data";
        } else if(curVal == -2) {
          sCurVal = "";
        } else {
          sCurVal = nf(curVal, 3, 1);
        } // if
        
        // update neighbourhood information
        nhui.drawDynamic(name, sCurVal);
        
      } // if
      
    } // for
    
  } //checkMouseOver
  
  // ----------------------------------------
  // pointInPolygon
  // checks if inputted point is within polygon;
  // returns true if so, false if not
  // ----------------------------------------
  boolean pointInPolygon(float[][] poly, float[] poin) {
    
    int i;
    int j = poly.length - 1;
    boolean oddNodes = false;
    
    for(i = 0; i < poly.length; i++) {
      
      if(((poly[i][1] < poin[1]) && (poly[j][1] >= poin[1])) ||
        ((poly[j][1] < poin[1]) && (poly[i][1] >= poin[1]))) {
          if((poly[i][0] + (poin[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) * (poly[j][0] - poly[i][0])) < poin[0]) {
            if(oddNodes) {
              oddNodes = false;
            } else {
              oddNodes = true;
            } // if
          } // if
        } // if
        
        j = i;
    } // for
    
    return oddNodes;    
    
  } // pointInPolygon
  
} // class

